﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Cosmos.Assembler;
using Cosmos.Assembler.x86;
using Cosmos.IL2CPU.Plugs;
using DimOS.Constants;
using DimOS.DimFS;
using IP = DimOS.Modules.InputProcessor;
using DimOS.Plugs;
using DimOS.VTS;
using UI = DimOS.DimSH.Interface;
using SYS = Cosmos.System;
using HAL = Cosmos.Hardware;
using CORE = Cosmos.Core;
using String = DimOS.Plugs.String;
using Console = System.Console;
using TERM = DimOS.DimSH.Terminal;
using DimOS.Internals;
using Array = DimOS.Plugs.Array;
using Convert = DimOS.Plugs.Convert;
using DateTime = DimOS.Plugs.DateTime;
// ReSharper disable SpecifyACultureInStringConversionExplicitly
// ReSharper disable ForCanBeConvertedToForeach

namespace DimOS.DimSH
{
    public static class Shell
    {
        public static int[] Buffer = new int[30000];
        public static int BufferPointer = 0;

        public static string WorkingDirectory = "#>user>home>";
        public static string CurrentTopic = "Placeholder";
        public static string TutorOutput = "Some text for testing purposes";
        public static ConsoleColor InputIndicatorColor = Colors.IndicatorInputDefault;
        public static Account AccessMode = Account.User;
        public static List<JournalEntry> Journal = new List<JournalEntry>();
        private static int _journalCounter;

        public static int TargetsDone = 1;
        public static int QuizzesDone = 2;
        public static int GuessesDone = 3;
        public static int OptionsDone = 4;
        public static int MatchesDone = 5;
        public static int ScriptsDone = 6;

        //?
        public static List<string> Targets = new List<string>();
        public static List<string> Quizzes = new List<string>();
        public static List<string> Guesses = new List<string>();
        public static List<string> Options = new List<string>();
        public static List<string> Matches = new List<string>();
        public static List<string> Scripts = new List<string>();

        public static List<CharColor> Center = new List<CharColor>();
        public static List<CharColor> Left = new List<CharColor>();
        public static List<CharColor> Right = new List<CharColor>();
        public static byte InputFitStatus = 3;

        public static void Initialize()
        {
            try
            {
                Screens.List.Add(new Screen());
                Screens.List.Add(new Screen());
                Screens.List.Add(new Screen());
                Screens.List.Add(new Screen());
                Screens.List.Add(new Screen());
                Screens.List.Add(new Screen());
                GC.Heap.Monitor();
            }
            catch { UI.InitIndicatorStatusFailed(); }

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoMemoryAllocated, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine((CORE.CPU.GetAmountOfRAM() + 2) + " MB", false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoMemoryUsed, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(((float)GC.Heap.MemoryUsed / 1024 / 1024)
            .ToString().Truncate(2) + " MB", false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoStorageUsed, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(((float)Layout.RootNode.Size / 1024)
            .ToString().Truncate(2) + " KB", false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoBuffer, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine((Buffer.Length * sizeof(int) / 1024) + " KB", false);

            Terminal.WriteLine(false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoScreen, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(UI.Columns + "x" + UI.Rows, false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoArchitecture, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(Captions.Architecture, false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoVersion, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(Captions.Version, false);

            Terminal.WriteLine(false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoTutor, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(Captions.Tutor, false);

            UI.Foreground = Colors.InitCommandShell;
            Terminal.Write("  " + Messages.InfoDeveloper, false);
            UI.Foreground = Colors.SectionOutputForeground;
            Terminal.WriteLine(Captions.Developer, false);
        }

        public static string Prompt(int startPosition, int endPosition, int row, int inputLimit = -1,
            bool useJournal = true, bool useHereStrings = true, List<CharColor> customPrompt = null,
            bool colorizeInput = true, bool allowSwitchScreens = true, bool inputValidation = true)
        {
            Center = new List<CharColor>();
            Left = new List<CharColor>();
            Right = new List<CharColor>();
            var entry = new JournalEntry();
            var chunk = new List<CharColor>();

            var JournalNode = new List<Node>(); if (useJournal) Layout.Search(null, null,
            new Attributes(true, false, false, false, true, false, false), -1, Condition
            .Equal, null, Condition.Equal, null, Condition.Equal, null, Condition.Equal,
            Account.Root, ref JournalNode);

            var command = new List<CharColor>(); int hereStringCount = 0, counter = 0; 
            do { if (hereStringCount != 0) { command.Add(new CharColor(Tokens.NewLine,
                Colors.SectionInputForeground, Colors.SectionInputBackground)); if (UI
                .RowOffset == 0) { TERM.ScrollEnd(); UI.ClearPath(true); TERM.Scroll
                (false); UI.RowOffset++; UI.DrawPath(true); UI.DrawPanel(); } UI.Y =
                UI.Rows - 3; var text = chunk.Split(Tokens.NewLine, UI.OutputColumns);
                for (int i = 0; i < text.Count; i++) { TERM.Scroll(false); UI.X = 0;
                TERM.Print(text[i], 0, true, true); if (!Settings.AdvancedPaths) UI
                .DrawPanel(); if (UI.Rows - 2 - UI.RowOffset > 0) UI.RowOffset++; } } 

                UI.Y = row; UI.X = startPosition;
                if (customPrompt == null)
                {
                    UI.Foreground = Colors.PromptEnclosure; TERM.Write(Separators.PromptBraceLeft, false);        //[
                    UI.Foreground = Colors.PromptSystem; TERM.Write(Captions.SystemName, false);                  //DimOS
                    UI.Foreground = Colors.PromptEnclosure; TERM.Write(Separators.PromptDelimiter, false);        //.
                    UI.Foreground = Colors.PromptUser; TERM.Write(AccessMode == Account.Root ? Separators         //root or
                        .PromptRoot : Separators.PromptUser, false); UI.Foreground = Colors.PromptEnclosure;      //user
                    TERM.Write(Separators.PromptBraceRight, false); UI.Foreground = AccessMode == Account         //]
                        .Root ? Colors.PromptIndicatorRoot : Colors.PromptIndicatorUser; TERM.Write(AccessMode    //# or
                            == Account.Root ? Separators.IndicatorRoot : Separators.IndicatorUser, false);        //$
                    UI.Foreground = Colors.PromptEnclosure; TERM.Write(Separators.PromptSpecifier + " ", false);  //:
                }
                else TERM.Print(customPrompt, 0);

                if (endPosition > UI.OutputColumns - 1) endPosition = UI.OutputColumns - 1;
                if (startPosition < 0) startPosition = 0; if (endPosition - startPosition < 3) return "";
                var start = UI.X; if (endPosition > UI.OutputColumns - 1) endPosition = UI.OutputColumns - 1;

                UI.Foreground = Colors.SectionInputForeground; UI.Background = Colors.SectionInputBackground;
                TERM.Write(UI.OutputSpaceSplitter.Remove(0, start + UI.OutputColumns - endPosition), false, false, false, false);
                InputFitStatus = 3; UI.DrawInputFitIndicator(endPosition, row); UI.X = start; //Redraw input scrolling indicator

                var keyInfo = new ConsoleKeyInfo(); chunk.Clear();
                while (keyInfo.KeyChar != Tokens.NewLine && (inputLimit < 0
                || Left.Count + Center.Count + Right.Count < inputLimit))
                {   
                    GC.Heap.Monitor();
                    var until = HAL.RTC.Second + 1;
                    if (until > 59) until -= 60;

                    HAL.Keyboard.KeyMapping map; TERM.ShowCursor();
                    while (!HAL.Global.Keyboard.GetMapping(out map))
                    {
                        if (until != HAL.RTC.Second) continue;

                        TERM.HideCursor();
                        UI.DrawClock(false);
                        TERM.ShowCursor();

                        until = HAL.RTC.Second + 1;
                        if (until > 59) until -= 60;
                    }
                    if (map == null) continue; TERM.HideCursor();
                    GC.Heap.Reclaim();

                    keyInfo = new ConsoleKeyInfo(map.Value, map.Key, HAL.Global.Keyboard.ShiftPressed,
                        HAL.Global.Keyboard.AltPressed, HAL.Global.Keyboard.CtrlPressed);

                    if (keyInfo.KeyChar == Tokens.NewLine) break;

                    var pos = UI.X - start;
                    UI.Foreground = Colors.SectionInputForeground;
                    UI.Background = Colors.SectionInputBackground; 

                    if (map.Key == ConsoleKey.Backspace)
                    {
                        if (pos > 0 || Left.Count != 0)
                        {
                            if (pos == 0) Left.RemoveAt(Left.Count - 1); else
                            { Center.RemoveAt(pos - 1); if (Right.Count != 0)
                            { Center.Add(Right[0]); Right.RemoveAt(0); } UI.X--;
                            var x = UI.X; TERM.Print(Center, x - start);
                            if (UI.X < endPosition) { UI.Foreground =
                            Colors.SectionInputForeground; TERM.Write
                            (Tokens.Space, false, false, false, false); }
                            UI.X = x; } UpdateInputFitStatus(endPosition, row);

                            if (inputValidation) H.Engine.Check(CharColor
                            .ToString(Left) + CharColor.ToString(Center)
                            + CharColor.ToString(Right));
                        }
                        continue;
                    }

                    if (map.Key == ConsoleKey.Delete)
                    {
                        if (pos < Center.Count || Right.Count != 0)
                        {
                            if (pos == Center.Count) Right.RemoveAt(0); else
                            { Center.RemoveAt(pos); if (Right.Count != 0)
                            { Center.Add(Right[0]); Right.RemoveAt(0); }
                            var x = pos; TERM.Print(Center, x); if
                            (UI.X < endPosition) { UI.Foreground =
                            Colors.SectionInputForeground; TERM.Write
                            (Tokens.Space, false, false, false, false); } UI.X = x
                            + start; } UpdateInputFitStatus(endPosition, row);

                            if (inputValidation) H.Engine.Check(CharColor
                            .ToString(Left) + CharColor.ToString(Center)
                            + CharColor.ToString(Right));
                        }
                        continue;
                    }

                    if (map.Key == ConsoleKey.LeftArrow)
                    {
                        if (pos > 0) UI.X--;
                        else if (Left.Count != 0)
                        {
                            TERM.ScrollLeft(ref Left, ref Center, ref Right, endPosition - start + 1, true, true); UI.X = start;
                            TERM.Print(Center, 0, false); UI.X = start; UpdateInputFitStatus(endPosition, row); //Shift to left
                        }
                        continue;
                    }

                    if (map.Key == ConsoleKey.RightArrow)
                    {
                        if (pos < Center.Count && pos < endPosition - start - 1) UI.X++; 
                        else if (Right.Count != 0)
                        {
                            TERM.ScrollRight(ref Left, ref Center, ref Right, endPosition - start + 1, true, true); UI.X = start;
                            TERM.Print(Center, 0, false); //Shift to right if there is some text still left off the main screen
                        }
                        else if (Center.Count == endPosition - start)
                        {
                            TERM.ScrollRight(ref Left, ref Center, ref Right, endPosition - start + 1); UI.X = start;
                            TERM.Print(Center, 0); TERM.Write(Tokens.Space, false, false, false, false); //End-of-line
                        }
                        UpdateInputFitStatus(endPosition, row);
                        continue;
                    }

                    if (map.Key == ConsoleKey.PageUp) { if (hereStringCount == 0) TERM.ScrollUp(); continue; }

                    if (map.Key == ConsoleKey.PageDown) { if (hereStringCount == 0) TERM.ScrollDown(); continue; }

                    if (map.Key == ConsoleKey.UpArrow)
                    {
                        if (_journalCounter < Journal.Count && useJournal)
                        {
                            _journalCounter++; UI.X = start;
                            Left.Clear(); Right.Clear(); Center = Journal
                            [Journal.Count - _journalCounter].Command.Clone();
                            while (Center.Count > endPosition - start)
                            { Right.Add(Center[endPosition - start]);
                              Center.RemoveAt(endPosition - start); }                  
                            TERM.Write(UI.OutputSpaceSplitter.Remove(0,
                                start + UI.OutputColumns - endPosition),
                                false, false, false, false); UI.X = start;
                            UpdateInputFitStatus(endPosition, row);
                            TERM.Print(Center, 0, Right.Count == 0);

                            if (inputValidation) H.Engine.Check(CharColor
                            .ToString(Left) + CharColor.ToString(Center)
                            + CharColor.ToString(Right));
                        }
                        continue;
                    }

                    if (map.Key == ConsoleKey.DownArrow)
                    {
                        if (_journalCounter > 1 && useJournal)
                        {
                            _journalCounter--; UI.X = start;
                            Left.Clear(); Right.Clear(); Center = Journal
                            [Journal.Count - _journalCounter].Command.Clone();
                            while (Center.Count > endPosition - start)
                            { Right.Add(Center[endPosition - start]);
                              Center.RemoveAt(endPosition - start); }                  
                            TERM.Write(UI.OutputSpaceSplitter.Remove(0,
                                start + UI.OutputColumns - endPosition),
                                false, false, false, false); UI.X = start;
                            UpdateInputFitStatus(endPosition, row);
                            TERM.Print(Center, 0, Right.Count == 0);   
                
                            if (inputValidation) H.Engine.Check(CharColor
                            .ToString(Left) + CharColor.ToString(Center)
                            + CharColor.ToString(Right));
                        }
                        continue;
                    }

                    if (map.Key == ConsoleKey.F1) { if (Screens.List.Count >= 1 && hereStringCount
                    == 0 && allowSwitchScreens) TERM.SwitchScreen(Screens.List[0]); continue; }

                    if (map.Key == ConsoleKey.F2) { if (Screens.List.Count >= 2 && hereStringCount
                    == 0 && allowSwitchScreens) TERM.SwitchScreen(Screens.List[1]); continue; }

                    if (map.Key == ConsoleKey.F3) { if (Screens.List.Count >= 3 && hereStringCount
                    == 0 && allowSwitchScreens) TERM.SwitchScreen(Screens.List[2]); continue; }

                    if (map.Key == ConsoleKey.F4) { if (Screens.List.Count >= 4 && hereStringCount
                    == 0 && allowSwitchScreens) TERM.SwitchScreen(Screens.List[3]); continue; }

                    if (map.Key == ConsoleKey.F5) { if (Screens.List.Count >= 5 && hereStringCount
                    == 0 && allowSwitchScreens) TERM.SwitchScreen(Screens.List[4]); continue; }

                    if (map.Key == ConsoleKey.F6) { if (Screens.List.Count >= 6 && hereStringCount
                    == 0 && allowSwitchScreens) TERM.SwitchScreen(Screens.List[5]); continue; }

                    //if (map.Value == Tokens.HereStringLeft || map.Value == Tokens.HereStringRight)

                    if (colorizeInput/* && counter % 2 != 0*/) if (Settings.AdvancedHighlighting)
                    IP.Colorize(ref Left, ref Center, ref Right); else Colorize(keyInfo.KeyChar);

                    if (pos == Center.Count && Right.Count == 0)
                    {
                        Center.Add(new CharColor(keyInfo.KeyChar, UI.Foreground, UI.Background));
                        if (Center.Count > endPosition - 1 - start)
                        { TERM.ScrollRight(ref Left, ref Center, ref Right, endPosition - start + 1);
                        UI.X = start; TERM.Print(Center, 0); } else TERM.Print(Center[Center.Count - 1]);
                    }
                    else if (pos == endPosition - start - 1)
                    {
                        Center.Insert(Center.Count - 1, new CharColor(keyInfo.KeyChar, UI.Foreground, UI.Background));
                        TERM.ScrollRight(ref Left, ref Center, ref Right, endPosition - start + 1); UI.X = start;
                        TERM.Print(Center, 0); UI.X--; //Ensure that the buffer's next character is always displayed
                    }
                    else
                    {
                        List.Insert(ref Center, pos, new CharColor(keyInfo.KeyChar, UI.Foreground, UI.Background)); //Insert at current pos
                        if (Center.Count > endPosition - start) TERM.ScrollLeft(ref Left, ref Center, ref Right, endPosition - start + 1);
                        TERM.Print(Center, pos, false); pos++; if (pos < endPosition - start) UI.X = pos + start; else UI.X = pos + start - 1;
                    }
                    UpdateInputFitStatus(endPosition, row); if (inputValidation) { H.Engine.Check
                    (CharColor.ToString(Left) + CharColor.ToString(Center) + CharColor.ToString(Right)); }
                }
                for (int i = 0; i < Left.Count; i++) { chunk.Add(Left[i]); command.Add(Left[i]); }
                for (int i = 0; i < Center.Count; i++) { chunk.Add(Center[i]); command.Add(Center[i]); }
                for (int i = 0; i < Right.Count; i++) { chunk.Add(Right[i]); command.Add(Right[i]); }
                Left.Clear(); Center.Clear(); Right.Clear(); if (!useHereStrings) break;

                if (useJournal) { if (hereStringCount == 0) { entry.Timestamp = DateTime
                .Now; entry.Command = chunk.Clone(); Journal.Add(entry); } else Journal
                .Add(new JournalEntry(chunk.Clone(), "", entry)); _journalCounter = 0; }

                if (chunk.Count != 0 && ((chunk[0].Character == Tokens.HereStringLeft
                || chunk[0].Character == Tokens.HereStringRight))) hereStringCount++;

                for (int i = 1; i < chunk.Count; i++) if ((chunk[i].Character == Tokens
                .HereStringLeft || chunk[i].Character == Tokens.HereStringRight) &&
                (chunk[i - 1].Character != Tokens.MirrorLeft && chunk[i - 1]
                .Character != Tokens.MirrorRight)) hereStringCount++;

            } while (hereStringCount % 2 != 0); if (!useHereStrings && useJournal)
            { entry.Timestamp = DateTime.Now; entry.Command = chunk.Clone();
              entry.Commentary = ""; Journal.Add(entry); } TERM.FinishInput();

            var result = CharColor.ToString(command); if (useJournal && JournalNode.Count !=
            0) JournalNode[0].Content.Data = (JournalNode[0].Content.Data.Decode() + Tokens
            .ValueLeft + entry.Timestamp + (entry.Commentary != "" ? Tokens.Space + Tokens
            .Convert.ToString() + " " + entry.Commentary : "") + Tokens.ValueRight + Tokens
            .NewLine + result + Tokens.NewLine + UI.JournalSplitter + Tokens.NewLine).Encode();

            if (!useHereStrings || string.IsNullOrEmpty(result)) return result; var interpolated
            = (result[0] == Tokens.HereStringLeft || result[0] == Tokens.HereStringRight) ? "" :
            result[0].ToString(); for (int i = 1; i < result.Length; i++) if((result[i] != Tokens
            .HereStringLeft && result[i] != Tokens.HereStringRight) || (result[i - 1] == Tokens
            .MirrorLeft || result[i - 1] == Tokens.MirrorRight)) interpolated += result[i]; result
            = interpolated.Unmirror(Tokens.HereStringLeft).Unmirror(Tokens.HereStringRight); return
            result;
        }

        public static void UpdateInputFitStatus(int x, int y)
        {
            var currentInputFitStatus = InputFitStatus;
            if (Left.Count == 0 && Right.Count != 0) InputFitStatus = 0; //Scroll right
            else if (Left.Count != 0 && Right.Count == 0) InputFitStatus = 1; //Scroll left
            else if (Left.Count != 0 && Right.Count != 0) InputFitStatus = 2; //Scroll both
            else InputFitStatus = 3; //No scrolling (when the input is not long enough)
            if (InputFitStatus != currentInputFitStatus) UI.DrawInputFitIndicator(x, y);
        }

        public static void Colorize(char character)
        {
            if (character == Tokens.Dot) UI.Foreground = Colors.SpecifierCommand;

            else if (character == Tokens.VerbatimString || character == Tokens.HereStringLeft
                || character == Tokens.HereStringRight) UI.Foreground = Colors.SpecifierString;

            else if (character == Separators.PathDelimiter
                || character == Separators.PathCurrent
                || character == Separators.PathParent)
                UI.Foreground = Colors.SeparatorPath;

            else if (character == Separators.IndicatorUser)
                UI.Foreground = Colors.PromptIndicatorUser;

            else if (character == Separators.IndicatorRoot)
                UI.Foreground = Colors.PromptIndicatorRoot;

            else if (character == Separators.PathTemp)
                UI.Foreground = Colors.PromptIndicatorTemp;

            else if (character == Tokens.MirrorRight) UI.Foreground = Colors.SpecifierMirror;

            else if (character == Tokens.CommandLeft || character == Tokens.CommandRight
                || character == Tokens.End) UI.Foreground = Colors.EnclosureCommand;

            else if (character == Tokens.ParameterLeft || character == Tokens.ParameterRight
                || character == Tokens.Separator) UI.Foreground = Colors.EnclosureFunction;

            else if (character == Tokens.ValueLeft
                || character == Tokens.ValueRight)
                UI.Foreground = Colors.EnclosureValue;

            else if (character == Tokens.Add || character == Tokens.Subtract
                || character == Tokens.Multiply  || character == Tokens.Divide
                || character == Tokens.Power || character == Tokens.Remainder)
                UI.Foreground = Colors.OperationsMath;

            else if (character == Tokens.Not || character == Tokens.IfShort[0] ||
            character == Tokens.Index) UI.Foreground = Colors.OperationsConditional;

            else if (character == Tokens.Convert || character == Tokens
                .SizeShort) UI.Foreground = Colors.OperationsLogical;

            else if (character == Tokens.Assignment) UI.Foreground = Colors.SpecifierValue;

            else if (Plugs.Char.IsDigit(character)) UI.Foreground = Colors.Digit;
        }
    }
}

